/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin;

import java.io.File;
import java.io.PrintStream;

import javax.xml.bind.JAXBException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;

import sh.grapendaal.tsuushin.service.BotService;
import sh.grapendaal.tsuushin.service.ConfigService;
import sh.grapendaal.tsuushin.service.ModuleService;
import sh.grapendaal.tsuushin.socket.IrcSocketFactory;

@Component
public class Main {
	private static final Logger logger = LoggerFactory.getLogger(Main.class);

	private static final String SPRING_CONFIGURATION_FILE_PREFIX = "spring";
	private static final String SPRING_CONFIGURATION_FILE_SUFFIX = ".xml";
	private static final String SPRING_TEST_CONFIGURATION = "test";

	// FIXME Hardcoded. Make this a command line parameter, default to
	// "configuration.xml" in working directory.
	private static final String IRCBOT_CONFIGURATION_FILE = "configuration.xml";

	@Autowired
	private ConfigService configService;

	@Autowired
	private ModuleService moduleService;

	@Autowired
	private BotService botService;

	/**
	 * Entry point to the application.
	 * 
	 * @param args
	 *            Command line arguments.
	 */
	public static void main(String[] args) {
		StringBuilder springConfigFile = new StringBuilder(SPRING_CONFIGURATION_FILE_PREFIX);
		if (args.length > 0 && SPRING_TEST_CONFIGURATION.equals(args[0])) {
			logger.debug("Running with test configuration.");
			springConfigFile.append("-").append(args[0]);
		}
		springConfigFile.append(SPRING_CONFIGURATION_FILE_SUFFIX);

		ApplicationContext context = new ClassPathXmlApplicationContext(springConfigFile.toString());
		context.getBean(Main.class).start();
	}

	/**
	 * Static class.
	 */
	private Main() {
	}

	/**
	 * Fire up all the services which make up the application. The rest will
	 * pick up from there.
	 */
	private void start() {
		// Prevent recursive logging while running in test mode.
		if (!configService.isTestEnvironment()) {
			setupLogger();
		}

		logger.debug("Starting application...");

		try {
			configService.loadConfiguration(new File(IRCBOT_CONFIGURATION_FILE));
		} catch (JAXBException e) {
			logger.error("Invalid configuration file: {}", e.getMessage(), e);
			stop(true);
		} catch (SAXException e) {
			logger.error("Invalid configuration file: {}", e.getMessage(), e);
			stop(true);
		} catch (Exception e) {
			logger.error("Could not parse configuration file.", e);
			stop(true);
		}

		try {
			moduleService.start();
		} catch (Exception e) {
			logger.error("Could not start the Module Service.", e);
			stop(true);
		}

		try {
			botService.start();
		} catch (Exception e) {
			logger.error("Could not start the Bot Service.", e);
			stop(true);
		}

		// Everything started nicely, now add the shutdown hook. If we did it
		// earlier, stop() would get called twice in case of an exception.
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			@Override
			public void run() {
				Main.this.stop();
			}
		}, "Shutdown Hook"));

		logger.debug("Done.");
	}

	/**
	 * Shuts down the services that need a clean shutdown.
	 */
	private void stop() {
		logger.debug("Stopping application...");

		botService.stop();
		moduleService.stop();
		IrcSocketFactory.shutdown();

		logger.debug("Done.");
	}

	/**
	 * Shuts down the services that need a clean shutdown and shuts down the
	 * application if callExit is true. The return status will be 1, because we
	 * wouldn't be calling System.exit() if everything is in order.
	 * 
	 * @param callExit
	 *            Whether to call System.exit().
	 */
	private void stop(boolean callExit) {
		this.stop();

		if (callExit) {
			// If we call System.exit() ourselves, then there is always
			// something wrong, hence status 1.
			System.exit(1);
		}
	}

	/**
	 * Sets up System.out and System.err so that those are actually calls to our
	 * logging mechanism. System.out are logged with level info, System.err are
	 * logged with level error.
	 */
	private void setupLogger() {
		System.setOut(new PrintStream(new LogStream() {
			private final Logger logger = LoggerFactory.getLogger("stdout");

			@Override
			protected void log(String line) {
				logger.info(line);
			}
		}));
		System.setErr(new PrintStream(new LogStream() {
			private final Logger logger = LoggerFactory.getLogger("stderr");

			@Override
			protected void log(String line) {
				logger.error(line);
			}
		}));
	}
}
